using System;
using System.Net;
using System.Security.Principal;
using System.Web.Services.Protocols;
using System.Windows.Forms;
using System.Xml;

using DeploymentExt.CrmSdk40;


namespace DeploymentExt
{
   /// <summary>
   /// Utility methods for communicating with Microsoft CRM 4.0
   /// </summary>
   public class Crm40Installer
   {
      protected bool bTesting = false;   // Activate for testing in the debugger
      protected int nMajorCrmVersion = 0;
      protected int nMinorCrmVersion = 0;
      protected MessageWindow msg = new MessageWindow();
      protected string strEmailNotice = "\r\n\r\nIf you need additional assistance, please send email to support@errorfreeorders.com\r\n";
    
      public int MajorCrmVersion
      {
         get { return nMajorCrmVersion; } 
      }

      public int MinorCrmVersion
      {
         get { return nMinorCrmVersion; } 
      }

      public Crm40Installer()
      {
      }

      public void PerformInstallation(string strUrl, string strOrganizationName)
      {
         string strXml = GetCustomizations(strUrl, strOrganizationName);   
         
         strXml = ModifyXml(strXml);

         ShowMessage("Transferring updated customizations to Microsoft CRM");
         SetCustomizations(strXml, strUrl, strOrganizationName);

         ShowMessage("Publishing Microsoft CRM customizations");
         PublishCustomizations(strUrl, strOrganizationName);
         HideMessage();
      }

      private void ShowMessage(string strMessage)
      {
         msg.Message = strMessage;
         msg.Show();
         msg.BringToFront();
         msg.Update();
      }

      private void HideMessage()
      {
         msg.Hide();
      }


      public ICredentials GetCredentials(string strServiceUrl)
      {
         ICredentials objCredentials = null;

         if (bTesting)
         {
            string strUserName = "Administrator";
            string strPassword = "pass@word1";
            string strDomain = "MOSS";
            string strServerName = "";

            // Instantiate a System.Net.CredentialCache object.
            System.Net.CredentialCache credentialCache = new System.Net.CredentialCache();

            // Instantiate a System.Net.NetworkCredential object passing in the Active Directory UserName,
            // Password and Domain that will be used to authenticate into the Microsoft CRM Security Model.
            // Set up this user in Microsoft CRM and give the user the appropriate License Keys and Roles.
            System.Net.NetworkCredential credentials =   new System.Net.NetworkCredential (
               strUserName,
               strPassword, 
               strDomain);
         
            // Add a value to the CredentialCache to indicate the URI of the Microsoft CRM Server, "NTLM",
            // which represents integrated Windows authenticated and the credentials of
            // the user who is to be used for authentication.
            Uri objUri = new Uri(strServiceUrl);
            strServerName = objUri.GetLeftPart(UriPartial.Authority);
            credentialCache.Add(new Uri(strServerName), "NTLM", credentials);
            objCredentials = credentialCache;
         }
         else
         {
            objCredentials = System.Net.CredentialCache.DefaultCredentials;
         }

         return objCredentials;
      }


      public bool GetCrmVersion(string strUrl, string strOrganizationName)
      {
         ShowMessage("Checking Microsoft CRM Version");
         bool bResult = true;
         CrmService  objService = new CrmService();

         try
         {
            CrmAuthenticationToken token = new CrmAuthenticationToken();
            token.AuthenticationType = 0; 
            token.OrganizationName = strOrganizationName;
            objService.Url = strUrl;
            objService.CrmAuthenticationTokenValue = token;
            objService.Credentials = GetCredentials(strUrl);
            RetrieveVersionRequest versionRequest = new RetrieveVersionRequest();
            RetrieveVersionResponse versionResponse = (RetrieveVersionResponse)objService.Execute(versionRequest);
            
            string[] objArray = versionResponse.Version.Split('.');
            nMajorCrmVersion = Convert.ToInt32(objArray[0]);
            nMinorCrmVersion = Convert.ToInt32(objArray[1]);  
         }
         catch (Exception)
         {
            // If the server was CRM 3.0, then this will fail
            bResult = false;
         }

         HideMessage();

         return bResult;
      }

      
      // Retrieve the existing CRM customizations
      public string GetCustomizations(string strUrl, string strOrganizationName)
      {
         ShowMessage("Retrieving existing customizations from Microsoft CRM");
         ExportXmlResponse entities = null;
         string strResult = "";

         try
         {
            CrmAuthenticationToken token = new CrmAuthenticationToken();
            token.AuthenticationType = 0; 
            token.OrganizationName = strOrganizationName;
         
            CrmService  objService = new CrmService();
            objService.Url = strUrl;
            objService.CrmAuthenticationTokenValue = token;
            objService.Credentials = GetCredentials(strUrl);

            ExportXmlRequest exportRequest = new ExportXmlRequest();

            // Define the entities to retrieve
            // The XML format must match this and the empty "nodes" node is required.
            exportRequest.ParameterXml = @"<importexportxml>
                                          <entities>
                                          <entity>quotedetail</entity>
                                          </entities>
                                          <nodes> 
                                             <node>isvconfig</node> 
                                          </nodes>
                                          <OrganizationSettings>
                                             <customization/>                                                                                             
                                          </OrganizationSettings>
                                          <securityroles>                                                                     
                                          </securityroles>
                                          <settings>
                                             <setting>customization</setting>
                                          </settings>                                           
                                          <workflows></workflows>
                                       </importexportxml>";
         
            // Execute the request.
            entities = (ExportXmlResponse)objService.Execute(exportRequest);
            strResult = entities.ExportXml;
         }
         catch (SoapException ex)
         {
            HideMessage();
            string strMessage = ex.Detail.InnerText;

            if (strMessage.IndexOf("CheckPrivilege") != -1)
            {
               MessageBox.Show("QuoteAdvantage can only be installed by a user with Administrative privileges inside of Microsoft CRM. \r\n\r\n" + strMessage + strEmailNotice, "QuoteAdvantage Setup");
            }
            else
            {
               MessageBox.Show("Failure while importing CRM customizations: " + ex.Detail.InnerText + strEmailNotice);
               MessageBox.Show(ex.StackTrace);
            }

            throw new ApplicationException("QuoteAdvantage installation could not be completed.");
         }

         HideMessage();

         return strResult;
      }


      // Write customizations to the CRM server
      public void SetCustomizations(string strXml, string strUrl, string strOrganizationName)
      {   
         try
         {
            CrmAuthenticationToken token = new CrmAuthenticationToken();
            token.AuthenticationType = 0; 
            token.OrganizationName = strOrganizationName;
         
            CrmService  objService = new CrmService();
            objService.Url = strUrl;
            objService.CrmAuthenticationTokenValue = token;
            objService.Credentials = GetCredentials(strUrl);

            ImportXmlRequest importRequest = new ImportXmlRequest();
            importRequest.CustomizationXml = strXml;

            importRequest.ParameterXml = @"<importexportxml>
                                          <entities>
                                          <entity>quotedetail</entity>
                                          </entities>
                                          <nodes> 
                                             <node>isvconfig</node> 
                                          </nodes>
                                          <securityroles>                                                                     
                                          </securityroles>
                                          <settings>
                                             <setting>customization</setting>
                                          </settings>                                           
                                          <workflows></workflows>
                                       </importexportxml>";

            // Execute the import.
            ImportXmlResponse importResponse = (ImportXmlResponse)objService.Execute((Request)importRequest);
         }
         catch (Exception ex)
         {
            MessageBox.Show("Unable to write customizations to Microsoft CRM: " + ex.Message); 
            throw ex;
         }
      }

      void PublishCustomizations(string strUrl, string strOrganizationName)
      {
         CrmAuthenticationToken token = new CrmAuthenticationToken();
         token.AuthenticationType = 0; 
         token.OrganizationName = strOrganizationName;
         
         CrmService  objService = new CrmService();
         objService.Url = strUrl;
         objService.CrmAuthenticationTokenValue = token;
         objService.Credentials = GetCredentials(strUrl);

         PublishXmlRequest objPublishRequest = new PublishXmlRequest();
         objPublishRequest.ParameterXml = @"<importexportxml>
                                          <entities>
                                          <entity>quotedetail</entity>
                                          </entities>
                                          <nodes> 
                                             <node>isvconfig</node> 
                                          </nodes>
                                          <securityroles>                                                                     
                                          </securityroles>
                                          <settings>
                                          </settings>                                           
                                          <workflows></workflows>
                                       </importexportxml>";
         PublishXmlResponse response = (PublishXmlResponse)objService.Execute(objPublishRequest);
      }

      string ModifyXml(string strXml)
      {
         XmlDocument    docXml = new XmlDocument();  

         docXml.LoadXml(strXml);           
         UpdateIsvConfig(docXml);
         UpdateQuoteDetail(docXml);
         UpdateSettings(docXml);

         return docXml.OuterXml;
      }

      private void UpdateIsvConfig(XmlDocument docXml)
      {
         XmlNode     nodeRoot = docXml.DocumentElement; 
         XmlNode     nodeEntityList = docXml.SelectSingleNode("ImportExportXml/IsvConfig/configuration/Entities");
         XmlNode     nodeQuote = nodeEntityList.SelectSingleNode("Entity[@name='quote']");
         XmlNode     nodeQuoteNavBar = null;
         XmlNode     nodeQuoteNavBarItem = null;
         XmlAttribute objAttrib = null;

         if (nodeQuote == null)
         {
            // Create the node for: <Entity name="quote">
            nodeQuote = docXml.CreateElement("", "Entity", "");
            nodeEntityList.AppendChild(nodeQuote);
                  
            objAttrib = docXml.CreateAttribute("", "name", "");
            objAttrib.Value = "quote";
            nodeQuote.Attributes.Append(objAttrib);
         }
    
         nodeQuoteNavBar = nodeQuote.SelectSingleNode("NavBar");

         if (nodeQuoteNavBar == null)
         {
            // Create the node for: <NavBar ValidForCreate="0" ValidForUpdate="1">
            nodeQuoteNavBar = docXml.CreateElement("", "NavBar", "");
            nodeQuote.AppendChild(nodeQuoteNavBar);
                  
            objAttrib = docXml.CreateAttribute("", "ValidForCreate", "");
            objAttrib.Value = "0";
            nodeQuoteNavBar.Attributes.Append(objAttrib);
                  
            objAttrib = docXml.CreateAttribute("", "ValidForUpdate", "");
            objAttrib.Value = "1";
            nodeQuoteNavBar.Attributes.Append(objAttrib);
         }

         nodeQuoteNavBarItem = nodeQuoteNavBar.SelectSingleNode("NavBarItem");
                
         if (nodeQuoteNavBarItem == null)
         {             
            nodeQuoteNavBarItem = CreateNavBarItem(docXml);
            nodeQuoteNavBar.AppendChild(nodeQuoteNavBarItem);
         }    
         else
         {
            nodeQuoteNavBarItem.Attributes["Icon"].Value = "/QuoteAdvantageWeb/QuoteAdvantageSmall.gif";
            nodeQuoteNavBarItem.Attributes["Url"].Value = "/QuoteAdvantageWeb/CustomizeQuote.aspx";
         }

         string strTest = nodeQuote.OuterXml;
      }      

      // Create the XML for:                                  
      //   <NavBarItem Icon="/QuoteAdvantageWeb/QuoteAdvantageSmall.gif" Url="/QuoteAdvantageWeb/CustomizeQuote.aspx" Id="QuoteAdvantage" AvailableOffline="false">
      //      <Titles>
      //       <Title LCID="1033" Text="QuoteAdvantage" />
      //      </Titles>
      //    </NavBarItem>  
      private XmlNode CreateNavBarItem(XmlDocument docXml)
      {
         XmlNode nodeQuoteNavBarItem = null;
         XmlAttribute objAttrib = null;

         nodeQuoteNavBarItem = docXml.CreateElement("", "NavBarItem", "");
               
         objAttrib = docXml.CreateAttribute("", "Icon", "");
         objAttrib.Value = "/QuoteAdvantageWeb/QuoteAdvantageSmall.gif";
         nodeQuoteNavBarItem.Attributes.Append(objAttrib);                    
                  
         objAttrib = docXml.CreateAttribute("", "Url", "");
         objAttrib.Value = "/QuoteAdvantageWeb/CustomizeQuote.aspx";
         nodeQuoteNavBarItem.Attributes.Append(objAttrib);
                  
         objAttrib = docXml.CreateAttribute("", "Id", "");
         objAttrib.Value = "QuoteAdvantage";
         nodeQuoteNavBarItem.Attributes.Append(objAttrib);
                  
         objAttrib = docXml.CreateAttribute("", "AvailableOffline", "");
         objAttrib.Value = "true";
         nodeQuoteNavBarItem.Attributes.Append(objAttrib);

         XmlNode nodeTitles = docXml.CreateElement("", "Titles", "");
         nodeQuoteNavBarItem.AppendChild(nodeTitles);

         XmlNode nodeTitle = docXml.CreateElement("", "Title", "");
         nodeTitles.AppendChild(nodeTitle);

         objAttrib = docXml.CreateAttribute("", "LCID", "");
         objAttrib.Value = "1033";
         nodeTitle.Attributes.Append(objAttrib);
         
         objAttrib = docXml.CreateAttribute("", "Text", "");
         objAttrib.Value = "QuoteAdvantage";
         nodeTitle.Attributes.Append(objAttrib);

         return nodeQuoteNavBarItem;
      }

      private void UpdateQuoteDetail(XmlDocument docXml)
      {
         XmlNode     nodeRoot = docXml.DocumentElement; 
         XmlNode     nodeEntity = docXml.SelectSingleNode("ImportExportXml/Entities/Entity/EntityInfo/entity[@Name='QuoteDetail']");
         XmlNode     nodeAttributes = nodeEntity.SelectSingleNode("attributes");
         XmlNode     nodeConfigCategoryId = nodeAttributes.SelectSingleNode("attribute[@PhysicalName='Qadv_config_category_id']");
         XmlNode     nodeConfigParentId = nodeAttributes.SelectSingleNode("attribute[@PhysicalName='Qadv_config_parent_id']");
         XmlNode     nodeAllowDiscounting = nodeAttributes.SelectSingleNode("attribute[@PhysicalName='QAdv_allow_discounting']");
         XmlNode     nodePercentDiscount = nodeAttributes.SelectSingleNode("attribute[@PhysicalName='QAdv_PercentDiscount']");
         XmlNode     nodePercentMarkup = nodeAttributes.SelectSingleNode("attribute[@PhysicalName='QAdv_PercentMarkup']");

         if (nodeConfigCategoryId == null)
         {
            CreateConfigCategoryId(nodeAttributes);
         }

         if (nodeConfigParentId == null)
         {
            CreateConfigParentId(nodeAttributes);
         } 

         if (nodeAllowDiscounting == null)
         {
            CreateAllowDiscounting(nodeAttributes);
         } 

         if (nodePercentDiscount == null)
         {
            CreatePercentDiscount(nodeAttributes);
         } 

         if (nodePercentMarkup == null)
         {
            CreatePercentMarkup(nodeAttributes);
         } 

      }

      private void UpdateSettings(XmlDocument docXml)
      {
         XmlNode     nodeRoot = docXml.DocumentElement; 
         XmlNode     nodeCustomization = docXml.SelectSingleNode("ImportExportXml/OrganizationSettings/customization");
         XmlNode     nodeIsvIntegration = nodeCustomization.SelectSingleNode("isvintegrationcode");

         if (nodeIsvIntegration == null)
         {
            nodeIsvIntegration = docXml.CreateElement("", "isvintegration", "");
            nodeIsvIntegration.InnerText = "7";
            nodeCustomization.AppendChild(nodeIsvIntegration);
         }
         else
         {
            nodeIsvIntegration.InnerText = "7";
         }
      }

      private void CreateConfigCategoryId(XmlNode nodeAttributes)
      {
         XmlNode nodeNew = nodeAttributes.OwnerDocument.CreateElement("", "attribute", "");
         XmlAttribute objAttrib = nodeAttributes.OwnerDocument.CreateAttribute("", "PhysicalName", "");
         objAttrib.Value = "Qadv_config_category_id";
         nodeNew.Attributes.Append(objAttrib);
         nodeNew.InnerXml = @"   <Type>nvarchar</Type>
                                 <Length>256</Length>
                                 <ValidForCreateApi>1</ValidForCreateApi>
                                 <ValidForUpdateApi>1</ValidForUpdateApi>
                                 <ValidForReadApi>1</ValidForReadApi>
                                 <IsCustomField>1</IsCustomField>
                                 <AttributeTypeId>00000000-0000-0000-00aa-11000000001e</AttributeTypeId>
                                 <DisplayMask>ValidForAdvancedFind|ValidForForm|ValidForGrid</DisplayMask>
                                 <Descriptions>
                                    <Description description=""ID of the category that an item was added from"" languagecode=""1033"" />
                                 </Descriptions>
                                 <ImeMode>auto</ImeMode>
                                 <RequiredLevel>none</RequiredLevel>
                                 <Format>text</Format>
                                 <MaxLength>128</MaxLength>
                                 <displaynames>
                                    <displayname description=""config_category_id"" languagecode=""1033"" />
                                 </displaynames>";

         nodeAttributes.AppendChild(nodeNew);
      }

      private void CreateConfigParentId(XmlNode nodeAttributes)
      {
         XmlNode nodeNew = nodeAttributes.OwnerDocument.CreateElement("", "attribute", "");
         XmlAttribute objAttrib = nodeAttributes.OwnerDocument.CreateAttribute("", "PhysicalName", "");
         objAttrib.Value = "Qadv_config_parent_id";
         nodeNew.Attributes.Append(objAttrib);
         nodeNew.InnerXml = @"   <Type>nvarchar</Type>
                                 <Length>80</Length>
                                 <ValidForCreateApi>1</ValidForCreateApi>
                                 <ValidForUpdateApi>1</ValidForUpdateApi>
                                 <ValidForReadApi>1</ValidForReadApi>
                                 <IsCustomField>1</IsCustomField>
                                 <AttributeTypeId>00000000-0000-0000-00aa-11000000001e</AttributeTypeId>
                                 <DisplayMask>ValidForAdvancedFind|ValidForForm|ValidForGrid</DisplayMask>
                                 <Descriptions>
                                    <Description description=""QuoteDetailId of a line item's parent item on a quote."" languagecode=""1033"" />
                                 </Descriptions>
                                 <ImeMode>auto</ImeMode>
                                 <RequiredLevel>none</RequiredLevel>
                                 <Format>text</Format>
                                 <MaxLength>40</MaxLength>
                                 <displaynames>
                                    <displayname description=""config_parent_id"" languagecode=""1033"" />
                                 </displaynames>";

         nodeAttributes.AppendChild(nodeNew);
      }


      private void CreateAllowDiscounting(XmlNode nodeAttributes)
      {
         XmlNode nodeNew = nodeAttributes.OwnerDocument.CreateElement("", "attribute", "");
         XmlAttribute objAttrib = nodeAttributes.OwnerDocument.CreateAttribute("", "PhysicalName", "");
         objAttrib.Value = "QAdv_allow_discounting";
         nodeNew.Attributes.Append(objAttrib);
         nodeNew.InnerXml = @"   <Type>bit</Type>
                                 <ValidForCreateApi>1</ValidForCreateApi>
                                 <ValidForUpdateApi>1</ValidForUpdateApi>
                                 <ValidForReadApi>1</ValidForReadApi>
                                 <IsCustomField>1</IsCustomField>
                                 <AttributeTypeId>00000000-0000-0000-00aa-110000000013</AttributeTypeId>
                                 <DisplayMask>ValidForAdvancedFind|ValidForForm|ValidForGrid</DisplayMask>
                                 <Descriptions>
                                    <Description description=""QuoteAdvantage flag to indicate whether or not discounting is allowed for this item"" languagecode=""1033"" />
                                 </Descriptions>
                                 <ImeMode>auto</ImeMode>
                                 <RequiredLevel>none</RequiredLevel>
                                 <AppDefaultValue>1</AppDefaultValue>
                                 <options>
                                    <option value=""0"">
                                       <labels>
                                       <label description=""No"" languagecode=""1033"" />
                                       </labels>
                                    </option>
                                    <option value=""1"">
                                       <labels>
                                       <label description=""Yes"" languagecode=""1033"" />
                                       </labels>
                                    </option>
                                 </options>
                                 <displaynames>
                                    <displayname description=""allow_discounting"" languagecode=""1033"" />
                                 </displaynames> ";

         nodeAttributes.AppendChild(nodeNew);

         // Add second attribute node for "qadv_allow_discountingName" 
         XmlNode nodeName = nodeAttributes.OwnerDocument.CreateElement("", "attribute", "");
         XmlAttribute objNameAttrib = nodeAttributes.OwnerDocument.CreateAttribute("", "PhysicalName", "");
         objNameAttrib.Value = "qadv_allow_discountingName";
         nodeName.Attributes.Append(objNameAttrib);
         nodeName.InnerXml = @"  <Type>virtual</Type>
                                 <ValidForReadApi>1</ValidForReadApi>
                                 <IsCustomField>1</IsCustomField>
                                 <IsLogical>1</IsLogical>
                                 <AttributeTypeId>00000000-0000-0000-00aa-110000000033</AttributeTypeId>
                                 <AttributeOf>QAdv_allow_discounting</AttributeOf>
                                 <XmlAbbreviation>name</XmlAbbreviation>
                                 <ImeMode>auto</ImeMode>
                                 <RequiredLevel>none</RequiredLevel> ";

         nodeAttributes.AppendChild(nodeName);
      }

      private void CreatePercentDiscount(XmlNode nodeAttributes)
      {
         XmlNode nodeNew = nodeAttributes.OwnerDocument.CreateElement("", "attribute", "");
         XmlAttribute objAttrib = nodeAttributes.OwnerDocument.CreateAttribute("", "PhysicalName", "");
         objAttrib.Value = "QAdv_PercentDiscount";
         nodeNew.Attributes.Append(objAttrib);
         nodeNew.InnerXml = @"   <Type>money</Type>
                                 <ValidForCreateApi>1</ValidForCreateApi>
                                 <ValidForUpdateApi>1</ValidForUpdateApi>
                                 <ValidForReadApi>1</ValidForReadApi>
                                 <IsCustomField>1</IsCustomField>
                                 <AttributeTypeId>00000000-0000-0000-00aa-11000000001a</AttributeTypeId>
                                 <DisplayMask>ValidForAdvancedFind|ValidForForm|ValidForGrid</DisplayMask>
                                 <Descriptions>
                                    <Description description=""QuoteAdvantage attribute for discounting quote detail items"" languagecode=""1033"" />
                                 </Descriptions>
                                 <ImeMode>auto</ImeMode>
                                 <RequiredLevel>none</RequiredLevel>
                                 <MinValue>0</MinValue>
                                 <MaxValue>1000000000</MaxValue>
                                 <Accuracy>4</Accuracy>
                                 <AccuracySource>0</AccuracySource>
                                 <displaynames>
                                    <displayname description=""PercentDiscount"" languagecode=""1033"" />
                                 </displaynames>";

         nodeAttributes.AppendChild(nodeNew);
      }

      private void CreatePercentMarkup(XmlNode nodeAttributes)
      {
         XmlNode nodeNew = nodeAttributes.OwnerDocument.CreateElement("", "attribute", "");
         XmlAttribute objAttrib = nodeAttributes.OwnerDocument.CreateAttribute("", "PhysicalName", "");
         objAttrib.Value = "QAdv_PercentMarkup";
         nodeNew.Attributes.Append(objAttrib);
         nodeNew.InnerXml = @"   <Type>money</Type>
                                 <ValidForCreateApi>1</ValidForCreateApi>
                                 <ValidForUpdateApi>1</ValidForUpdateApi>
                                 <ValidForReadApi>1</ValidForReadApi>
                                 <IsCustomField>1</IsCustomField>
                                 <AttributeTypeId>00000000-0000-0000-00aa-11000000001a</AttributeTypeId>
                                 <DisplayMask>ValidForAdvancedFind|ValidForForm|ValidForGrid</DisplayMask>
                                 <Descriptions>
                                    <Description description=""QuoteAdvantage attribute for increasing the price of quote detail items"" languagecode=""1033"" />
                                 </Descriptions>
                                 <ImeMode>auto</ImeMode>
                                 <RequiredLevel>none</RequiredLevel>
                                 <MinValue>0</MinValue>
                                 <MaxValue>1000000000</MaxValue>
                                 <Accuracy>4</Accuracy>
                                 <AccuracySource>0</AccuracySource>
                                 <displaynames>
                                    <displayname description=""PercentMarkup"" languagecode=""1033"" />
                                 </displaynames>";

         nodeAttributes.AppendChild(nodeNew);
      }


   } // end class
}
